/* Changes:
 *
 * added finally blocks to try blocks
 *   always put cleanup code (e.g. closing opened files) in finally blocks
 * added CLI argument support, removed hard-coded file names
 *   2 operating modes: stream operation and file operation, automatically 
 *   determined by number of arguments
 * removed requirement for named segments in input file 
 *   now complies with the standard gss text format ('x1 y1 x2 y2\n')
 *   program now generates internal id's (currently just an int index)
 * uses JRE properties to control output
 */
package gss;

import static gss.generator.GeneratorUtils.*;

import org.apache.commons.math.fraction.BigFraction;
import java.math.BigInteger;

import java.io.Writer;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;

import java.io.Reader;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.InputStreamReader;

import java.io.FileNotFoundException;
import java.io.IOException;

import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Scanner;

/**
 * @author mdm32
 */
public class GSSToMatrix {
    private static final String FRAC_REGEX = "(\\d+)(\\s*/\\s*(\\d+))?";

    /**
     * file extension for 0-1 matrix output
     */
    private static final String OUT = "matrix";

    /** Preprocessor reads a set of line segments, one per line via standard
     * input or a file (depending on argument), and outputs a 0-1 incidence
     * matrix of all vertices. If no arguments are given, all input and
     * output is done via standard input and output. The options Property
     * must be set to "quiet" or debugging garbage will get mixed in to the
     * output. In file mode, command line arguments specify the input file
     * and optionally the output file. If no output file is specified, the
     * output file is obtained by appending an extension to the input file.
     * Output is a 0-1 * space- separated incidence matrix.
     *
     * @param args  Command line arguments: input_format [input_file
     *		    [output_file]] input_format should be "f" for frac and
     *		    "d" for double
     */
    public static void main(String[] args)
	throws FileNotFoundException, IOException
    {

        if (args.length < 3) {
            System.err.println( "Usage: f|d gss_input_file" +
		    " matrix_file intersection_file");
            System.exit(1);
        }

        // if true, reads in "string / string"
        // if false, reads in "double"
        boolean readsBigFrac = false;
        if (args[0].equals("f")) {
            readsBigFrac = true;
        } else if (args[0].equals("d")) {
            readsBigFrac = false;
        } else { // something wrong
            System.err.println("Unrecognized input file format;" + 
		    " please use: (f)rac | (d)ouble");
            System.exit(1);
        }

	String gssFile	  = args[1];
	String matrixFile = args[2];
	String vertexFile = args[3];

        //create slot for GSS structure to be held
        Set<LineSegment> segments = new LinkedHashSet<LineSegment>();

        // process Properties
	// see java.sun.com/docs/books/tutorial/
	// essential/environment/properties.html
        String options = System.getProperty("options");
        if (options == null) {
            options = "";
        }

        // true to print debug output, on by default
        boolean printflag = !options.matches("quiet");

        // Process CLI arguments for INPUT ONLY
        Scanner scanner = null;
        String input_file = null;
	if (readsBigFrac) {
	    segments = readSegments(gssFile);
	} else {
	    try {
		scanner = new Scanner(
			new BufferedReader(new FileReader(gssFile)));
		try {
		    while (scanner.hasNext()) {
			BigFraction cx1 =
			    new BigFraction(scanner.nextDouble());
			BigFraction cy1 =
			    new BigFraction(scanner.nextDouble());
			BigFraction cx2 =
			    new BigFraction(scanner.nextDouble());
			BigFraction cy2 =
			    new BigFraction(scanner.nextDouble());
			segments.add(new LineSegment(cx1, cy1, cx2, cy2));
		    }
		} finally {
		    if (scanner != null) { scanner.close(); }
		}
            } catch (Exception e) {
		System.err.println(e.getMessage());
		e.printStackTrace();
	    }
        } 

        /* print line segments
        if (printflag) {
            for (LineSegment seg : segments) {
                System.err.println(seg);
            }
            System.err.println("\n");
        } */

        //figure out the intersections
        List<Vertex> vertices = new ArrayList<Vertex>();
	for (LineSegment seg1 : segments) {
	    for(LineSegment seg2 : segments)
		if (!seg1.equals(seg2) && !seg1.isParallelTo(seg2) 
			&& seg1.intersects(seg2))
		{
		    addVertex(seg1.intersectionPoint(seg2), vertices);
		}
	}

	try{
	    BufferedWriter matrixOut =
		new BufferedWriter(new FileWriter(matrixFile)); 
	    BufferedWriter vertexOut =
		new BufferedWriter(new FileWriter(vertexFile));

	    try {
		for(Vertex v : vertices) {
		    // write vertex's matrix row to matrix file
		    matrixOut.write(v.toIncidenceMatrixRow(segments));
		    matrixOut.newLine();

		    // write vertex coordinates to intersections file
		    vertexOut.write(v.toString().replace(',', ' '));
		    vertexOut.newLine();
		}
	    } finally {
		if (matrixOut != null) { matrixOut.close(); }
		if (vertexOut !=  null) {vertexOut.close(); }
	    }
	} catch (IOException e){
	    System.err.println("Error: " + e.getMessage());
	    e.printStackTrace();
	} 
    }

    /**
     * Reads exact precision segments from the filename given
     * and returns a set of segments
     */
    public static Set<LineSegment> readSegments(String gssFile) {
	Set<LineSegment> segments = new LinkedHashSet<LineSegment>();
	try {
	    Scanner scanner = new Scanner(
		    new BufferedReader(new FileReader(gssFile)));
	    try {
		// declare some temporary variables
		String cx1n, cx1d, cx2n, cx2d;
		String cy1n, cy1d, cy2n, cy2d;

		BigFraction cx1, cy1, cx2, cy2;
		while (scanner.hasNext()) {
		    String line = scanner.nextLine();
		    Scanner line_scanner = new Scanner(line);
		    java.util.regex.MatchResult matcher = null;

		    line_scanner.findInLine(FRAC_REGEX);
		    matcher = line_scanner.match();
		    cx1n = matcher.group(1);
		    cx1d = matcher.group(3);
		    if (cx1d == null || cx1d.equals("")) {
			cx1d = "1";
		    }

		    line_scanner.findInLine(FRAC_REGEX);
		    matcher = line_scanner.match();
		    cy1n = matcher.group(1);
		    cy1d = matcher.group(3);
		    if (cy1d == null || cy1d.equals("")) {
			cy1d = "1";
		    }

		    line_scanner.findInLine("(\\d+)(\\s*/\\s*(\\d+))?");
		    matcher = line_scanner.match();
		    cx2n = matcher.group(1);
		    cx2d = matcher.group(3);
		    if (cx2d == null || cx2d.equals("")) {
			cx2d = "1";
		    }

		    line_scanner.findInLine("(\\d+)(\\s*/\\s*(\\d+))?");
		    matcher = line_scanner.match();
		    cy2n = matcher.group(1);
		    cy2d = matcher.group(3);
		    if (cy2d == null || cy2d.equals("")) {
			cy2d = "1";
		    }

		    cx1 = new BigFraction(
			    new BigInteger(cx1n), new BigInteger(cx1d));
		    cy1 = new BigFraction(
			    new BigInteger(cy1n), new BigInteger(cy1d));
		    cx2 = new BigFraction(
			    new BigInteger(cx2n), new BigInteger(cx2d));
		    cy2 = new BigFraction(
			    new BigInteger(cy2n), new BigInteger(cy2d));

		    segments.add(new LineSegment(cx1, cy1, cx2, cy2));
		}
	    } finally {
		if (scanner != null) { scanner.close(); }
	    }
	} catch (Exception e) {
	    System.err.println(e.getMessage());
	    e.printStackTrace();
	}
	return segments;
    }

    /*shoots back (n,m), the indices of the first two parallel segments
     * that intersect
    public static int[] parallelInt(Vector<LineSegment> gss) {
        for (int i = 0; i < gss.size(); i++) {
            for (int j = 0; j < gss.size(); j++) {
                if (i != j) {
                    if (gss.elementAt(i).isParallelTo(gss.elementAt(j))) {
                        if (gss.elementAt(i).intersects(gss.elementAt(j))) {
                            int[] a = new int[2];
                            a[0] = i;
                            a[1] = j;
                            return a;
                        }
                    }
                }
            }
        }
        int[] a = new int[2];
        a[0] = -1;
        a[1] = -1;
        return a;
    }
     */

}
